package main;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Scanner;

import odor.Odor;
import odor.OdorPreprocessor;

import structures.Network;
import util.FileUtil;
import util.Gaussian;
import util.SingleValueGaussian;
import util.SummedGaussian;
import util.Util;

/*
 *  THIS SOFTWARE IS PROTECTED UNDER THE MIT LICENSE FOR FREE AND OPEN-SOURCE SOFTWARE
 *  
 *  Copyright (C) 2011 by Thomas Cleland, Pedro Rittner
 *  
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *  
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 *  
 */

/*
 * GLOBAL TO-DO LIST
 * 
 * 1.  TODO Change all Odors to exponentials
 * 1a. TODO Write JUnit tests for Odor, Network 
 * 2.  TODO Get Standard I/O Working
 * 3.  TODO Setup command-line parameters
 * 4.  TODO Change the way Odors are saved to files and read
 */

/*
 * PRIORITY TO-DO LIST
 * 
 * FIXME Odors and things that use them.
 */
public class Main 
{
	public static boolean VERBOSE = false;
	
	public static final String PROGRAM_NAME = "Neuro-Baldr v1.00";
	
	public static final short PRECISION = 3;
	
	public static final int ODOR_INPUT_SUM = 1000;
	public static final int NUM_COLS = 20;
	
	//public static int UNIVERSAL_TO_MITRAL_WEIGHT = 1;
	
	public static BufferedWriter LOG_WRITER;
	
	
	public static void main(String[] args) throws IOException, InitializationException
	{        		
		//NeuroBaldrControlFrame f = new NeuroBaldrControlFrame(5, Odor.generateOdorBattery(5, NUM_COLS, ODOR_INPUT_SUM, 25));
		
		/*
		// DEMO
		SummedGaussian g = new SummedGaussian(100, 100, 25, 100, 1000);		
		Odor od1 = new Odor(NUM_COLS, g);
		
		Network n = new Network(NUM_COLS);
		n.initialize(null);
		
		double[] inputs = od1.getInputs(); 
		double[] outputs= n.fire(od1);
		
		Util.printArray(inputs);
		Util.printArray(outputs);
		*/
		
		Network n = new Network(NUM_COLS);
		n.initialize(null);
		
		Odor[] battery= OdorPreprocessor.generateSimilarOdors(2, NUM_COLS);
		
		double[][] outputs = new double[NUM_COLS][battery.length];
		
		int counter = 0;
		
		for (Odor od : battery)
		{
			System.out.println("Odor #"+(counter));
			
			for (double i : od.getInputs())
			{
				System.out.println(i);
			}
			
			System.out.println();
			
			outputs[counter] = n.fire(od);
			
			for (double a: outputs[counter])
			{
				System.out.println(a);
			}
			
			counter++;
		}
		
		System.out.println("-----------------");
		System.out.println("Angle Between Odors #0 & #1 PRE : "+Util.angleBetweenVectors(battery[0].getInputs(), battery[1].getInputs()));
		System.out.println("Angle Between Odors #0 & #1 POST: "+Util.angleBetweenVectors(outputs[0], outputs[1]));
		/*
		if (args != null && args.length != 0) 
		{
			Main.parseOutputFile(args[1]);
		}
		
		SummedGaussian g = new SummedGaussian(100, 100, 25, 100);		
		Odor[] arr = {new Odor(NUM_COLS, g)};
		
		Network n = parseParametersFile(args[2]);
		n.initialize(null);
		
		Odor[] battery = (args != null && args.length != 0)? Main.parseInputFile(args[0]) : (arr);
		
		for (Odor od : battery)
		{
			double[] output = n.fire(od);
			n.resetGranules();
			
			//LOG
			if (Main.LOG_WRITER != null)
			{
				Main.attemptNewLineToLog();
				Main.attemptWriteToLog("Angle b/w Input&Output = "+
						Math.acos( Util.dotProduct(od.getInputs(), output) / (Util.magnitude(od.getInputs()) * Util.magnitude(output)) ) );
			}
			
			//DEBUG
			if (Main.VERBOSE)
			{
				System.out.println();
				System.out.println("Angle b/w Input&Output = "+
						Math.acos( Util.dotProduct(od.getInputs(), output) / (Util.magnitude(od.getInputs()) * Util.magnitude(output)) ) );
			}
		}
		
		//LOG
		if (Main.LOG_WRITER != null)
		{
			Main.LOG_WRITER.close();
		}
		
		*/
	}
	
	//TODO parse network parameters file
	private static Network parseParametersFile(String filename)
	{
		if (filename == null || filename.compareTo("") == 0) return new Network(Main.NUM_COLS);
		
		File parametersFile = new File(System.getProperty("user.dir")+File.separatorChar+filename);
		
		//TODO Interesting Network variables
		Network network = null;
		int numCols = 0;
		int granulesPerColumn = 1;
		double intercolumnConnectivity = 0.20;
		boolean dynamicItercolumnConnectivity = false;
		Gaussian granuleToMitralWeight = null;
		Gaussian granuleActivationThreshold = null;
		Gaussian mitralToGranuleWeights = null;
		
		String[] lines = Util.truncateLinesAtChar(FileUtil.readFileLines(parametersFile), '=');
		
		if (lines == null || lines.length != 7) 
		{
			//DEBUG
			if (Main.VERBOSE)
			{
				System.out.println();
				System.out.println("-COULD NOT READ PARAMS FILE; DEFAULT NETWORK USED-");
			}
			
			//LOG
			if (Main.LOG_WRITER != null)
			{
				Main.attemptNewLineToLog();
				Main.attemptWriteToLog("-COULD NOT READ PARAMS FILE; DEFAULT NETWORK USED-");
			}
			
			return new Network(Main.NUM_COLS);
		}
		
		for (int x = 0; x < lines.length; x++)
		{
			switch (x)
			{
				case 0:
				{
					numCols = Integer.parseInt(lines[x]);
					break;
				}
				case 1:
				{
					granulesPerColumn = Integer.parseInt(lines[x]);
					break;
				}
				case 2:
				{
					intercolumnConnectivity = Double.parseDouble(lines[x]);
					break;
				}
				case 3:
				{
					dynamicItercolumnConnectivity = Boolean.parseBoolean(lines[x]);
					break;
				}
				case 4:
				{
					if (lines[x].contains( (CharSequence)"(" ))
					{
						lines[x] = Util.removeChar(lines[x], '(');
						lines[x] = Util.removeChar(lines[x], ')');
						
						granuleToMitralWeight = new Gaussian( Double.parseDouble(lines[x]) );
					}
					else
						granuleToMitralWeight = new SingleValueGaussian(Double.parseDouble(lines[x]));
					
					break;
				}
				case 5:
				{
					if (lines[x].contains( (CharSequence)"(" ))
					{
						lines[x] = Util.removeChar(lines[x], '(');
						lines[x] = Util.removeChar(lines[x], ')');
						
						granuleActivationThreshold = new Gaussian(Double.parseDouble(lines[x]));
					}
					else
						granuleActivationThreshold = new SingleValueGaussian(Double.parseDouble(lines[x]));
					
					break;
				}
				case 6:
				{
					if (lines[x].contains( (CharSequence)"(" ))
					{
						lines[x] = Util.removeChar(lines[x], '(');
						lines[x] = Util.removeChar(lines[x], ')');
						
						mitralToGranuleWeights = new Gaussian(Double.parseDouble(lines[x]));
					}
					else
						mitralToGranuleWeights = new SingleValueGaussian(Double.parseDouble(lines[x]));
					
					break;
				}
			}
		}
		
		network = new Network(  numCols,
								granulesPerColumn,
								intercolumnConnectivity,
								dynamicItercolumnConnectivity,
								granuleToMitralWeight,
								granuleActivationThreshold,
								mitralToGranuleWeights);
		
		
		return network;
	}
	
	//TODO write javadoc
	private static Odor[] parseInputFile(String filename)
	{
		File inputFile  = new File(System.getProperty("user.dir")+File.separatorChar+filename);
		
		Odor[] inputBattery = null;
		
		if (inputFile.exists() && inputFile.canRead())
		{
			inputBattery = Odor.fileToBattery(inputFile);
		}
		
		return inputBattery;
	}
	
	//TODO write javadoc
	private static void parseOutputFile(String fileName)
	{
		File outputFile;
		
		try
		{
			outputFile =  new File(System.getProperty("user.dir")+File.separatorChar+fileName);
			
			outputFile.createNewFile();
			
			Main.LOG_WRITER = new BufferedWriter(new FileWriter(outputFile));
			
			Main.attemptWriteToLog("SIMUL START "+DateFormat.getDateTimeInstance().format(System.currentTimeMillis()));
			Main.attemptNewLineToLog();
		}
		catch (IOException e)
		{
			javax.swing.JOptionPane.showMessageDialog(null, "Couldn't Create Output File: No Output Will be Written");
			Main.LOG_WRITER = null;
		}
	}
	
	/**
	 * Writes message to output file, if it exists.
	 * @param msg
	 */
	public static void attemptWriteToLog(String msg)
	{
		try
		{
			Main.LOG_WRITER.write(msg);
		}
		catch (IOException e)
		{
			System.err.println("Unable to write \""+msg+"\" to file");
		}
	}
	
	/**
	 * Writes newline character to output file, if it exists.
	 */
	public static void attemptNewLineToLog()
	{
		try
		{
			Main.LOG_WRITER.newLine();
		}
		catch (IOException e)
		{
			System.err.println("Unable to write newline to file");
		}
	}
}
